---
order: 2.5
category: '@threlte/extras'
sourcePath: 'packages/extras/src/lib/interactivity'
title: 'interactivity'
type: 'plugin'
---

To add click, pointer and wheel events to your Threlte app use the `interactivity` plugin.

```svelte title="Scene.svelte" {2,3}+
<script>
  import { interactivity } from '@threlte/extras'
  interactivity()
</script>
```

<Example path="extras/interactivity" />

All child components can now make use of the new events.

```svelte title="Scene.svelte" {7-9}+
<script>
  import { interactivity } from '@threlte/extras'
  interactivity()
</script>

<T.Mesh
  onclick={() => {
    console.log('clicked')
  }}
>
  <T.BoxGeometry />
  <T.MeshStandardMaterial color="red" />
</T.Mesh>
```

## Available events

The following interaction events are available:

```svelte
<T.Mesh
  onclick={(e) => console.log('click')}
  oncontextmenu={(e) => console.log('context menu')}
  ondblclick={(e) => console.log('double click')}
  onwheel={(e) => console.log('wheel')}
  onpointerup={(e) => console.log('up')}
  onpointerdown={(e) => console.log('down')}
  onpointerover={(e) => console.log('over')}
  onpointerout={(e) => console.log('out')}
  onpointerenter={(e) => console.log('enter')}
  onpointerleave={(e) => console.log('leave')}
  onpointermove={(e) => console.log('move')}
  onpointermissed={() => console.log('missed')}
/>
```

## Event data

All interaction events contain the following data:

```typescript
type Event = THREE.Intersection & {
  intersections: THREE.Intersection[] // The first intersection of each intersected object
  object: THREE.Object3D // The object that was actually hit
  eventObject: THREE.Object3D // The object that registered the event
  camera: THREE.Camera // The camera used for raycasting
  delta: THREE.Vector2 //  Distance between mouse down and mouse up event in pixels
  nativeEvent: MouseEvent | PointerEvent | WheelEvent // The native browser event
  pointer: Vector2 // The pointer position in normalized device coordinates
  ray: THREE.Ray // The ray used for raycasting
  stopPropagation: () => void // Function to stop propagation of the event
  stopped: boolean // Whether the event propagation has been stopped
}
```

## Event propagation

Propagation works a bit differently to the DOM because objects can occlude each other in 3D.
The intersections array in the event data includes all objects intersecting the ray, not just the
nearest. Only the first intersection with each object is included. The event is first delivered
to the object nearest the camera, and then bubbles up through its ancestors like in the DOM.
After that, it is delivered to the next nearest object, and then its ancestors, and so on.
This means objects are transparent to pointer events by default, even if the object handles
the event.

`event.stopPropagation()` doesn't just stop this event from bubbling up, it also stops it from
being delivered to farther objects (objects behind this one). All other objects, nearer or farther,
no longer count as being hit while the pointer is over this object. If they were previously
delivered pointerover events, they will immediately be delivered pointerout events. If you want
an object to block pointer events from objects behind it, it needs to have an event handler as follows:

```svelte
<T.Mesh onclick={(e) => e.stopPropagation()} />
```

even if you don't want this object to respond to the pointer event. If you do want to handle the
event as well as using `stopPropagation()`, remember that the `pointerout` events will happen during
the `stopPropagation()` call. You probably want your other event handling to happen after this.

## Event targets

If no event target is specified, all event handlers listen to events on the `domElement` of the
`renderer` (which is the canvas element by default). You can specify a different target by passing
a `target` prop to the `interactivity` plugin.

```svelte title="Scene.svelte"
<script>
  import { interactivity } from '@threlte/extras'

  interactivity({
    target: document
  })
</script>
```

It's also possible to change the target at runtime by updating the store `target` returned from the
`interactivity` plugin.

```svelte title="Scene.svelte"
<script>
  import { interactivity } from '@threlte/extras'

  const { target } = interactivity()

  $effect(() => {
    target.set(document)
  })
</script>
```

## Compute function

In the event that your event target is not the same size as the canvas, you can pass a `compute` function
to the `interactivity` plugin. This function receives the DOM event and the interactivity state and should
set the `pointer` property of the state to the pointer position in normalized device coordinates as well
as set the raycaster up for raycasting.

```svelte title="Scene.svelte"
<script>
  import { interactivity } from '@threlte/extras'
  import { useThrelte } from '@threlte/core'

  const { camera } = useThrelte()

  interactivity({
    compute: (event, state) => {
      // Update the pointer
      state.pointer.update((p) => {
        p.x = (event.clientX / window.innerWidth) * 2 - 1
        p.y = -(event.clientY / window.innerHeight) * 2 + 1

        return p
      })

      // Update the raycaster
      state.raycaster.setFromCamera(state.pointer.current, $camera)
    }
  })
</script>
```

## Event filtering

You can filter and sort events by passing a `filter` to the `interactivity` plugin. The function receives all hits and the interactivity state and should return the hits that should be delivered to the event handlers in the order they should be delivered.

```svelte title="Scene.svelte"
<script>
  import { interactivity } from '@threlte/extras'

  interactivity({
    filter: (hits, state) => {
      // Only return the first hit
      return hits.slice(0, 1)
    }
  })
</script>
```

## Interactivity state

To access the interactivity state, you can use the `useInteractivity` hook in any child component of the component that implements the `interactivity` plugin as follows:

```svelte title="Child.svelte"
<script>
  import { useInteractivity } from '@threlte/extras'

  const { pointer, pointerOverTarget } = useInteractivity()

  $inspect($pointer, $pointerOverTarget)
</script>
```

where this is the type of the interactivity state:

```ts
export type State = {
  enabled: CurrentWritable<boolean>
  target: CurrentWritable<HTMLElement | undefined>
  pointer: CurrentWritable<Vector2>
  pointerOverTarget: CurrentWritable<boolean>
  lastEvent: MouseEvent | WheelEvent | PointerEvent | undefined
  raycaster: Raycaster
  initialClick: [x: number, y: number]
  initialHits: THREE.Object3D[]
  hovered: Map<string, IntersectionEvent<MouseEvent | WheelEvent | PointerEvent>>
  interactiveObjects: THREE.Object3D[]
  compute: ComputeFunction
  filter?: FilterFunction
}
```

<Tip type="tip">
  [`CurrentWritable`](/docs/reference/core/utilities#currentwritable) is a custom Threlte store.
  It's a regular writable store that also has a `current` property which is the current value of the
  store. It's useful for accessing the value of a store in a non-reactive context, such as in loops.
</Tip>

## TypeScript

### Prop types

By default, the `interactivity` plugin does not add any prop types to the `<T>`
component. You can however extend the types of the `<T>` component by defining
the `Threlte.UserProps` type in your ambient type definitions. In a typical
SvelteKit application, you can find these [in
`src/app.d.ts`](https://svelte.dev/docs/kit/types#app.d.ts). The interactivity
plugin exports the `InteractivityProps` type which you can use as shown below:

```ts title="src/app.d.ts"
import type { InteractivityProps } from '@threlte/extras'

declare global {
  namespace Threlte {
    interface UserProps extends InteractivityProps {}
  }
}

export {}
```

Now all event handlers on `<T>` components will be type safe.

```svelte title="Scene.svelte"
<script>
  import { interactivity } from '@threlte/extras'
  interactivity()
</script>

<T.Mesh
  onclick={(e) => {
    // e: IntersectionEvent<MouseEvent>
  }}
/>
```
